use std::{
    sync::{atomic::*, LazyLock, Mutex}, thread::{self, JoinHandle}, time::Duration,
};

use crate::{
    defines::*, log, config::Config, utils::{self, lock_free_dt::{Queue,UltraHashMap,IndexedList}},
    game::*, engine_ai,
};

const PARAM_UCT_TIMES_DEFAULT:isize = 16384;
const PARAM_UCT_TIMES_MIN:isize = 4096;
const PARAM_UCT_TIMES_MAX:isize = 1048576;
const PARAM_LEVEL_DEFAULT:isize = 1;
const PARAM_LEVEL_MIN:isize = 0;
const PARAM_LEVEL_MAX:isize = 9;
const INSTANCE_MAX_NUMS:usize = CLIENT_MAX_NUMS;
const INSTANCE_TASK_CAPACITY:usize = 8;
const MESSAGE_QUEUE_MAX_SIZE:usize = 64;
const PARAMS_PART_SIZE:usize = 16;
#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash)]
pub enum ParamIntKind {
    UctTimes,
    Level,
}
fn param_int_default(kind:ParamIntKind) -> isize {
    match kind {
        ParamIntKind::UctTimes => {PARAM_UCT_TIMES_DEFAULT}
        ParamIntKind::Level => {PARAM_LEVEL_DEFAULT}
    }
}
fn param_int_clamp(kind:ParamIntKind, a:isize) -> isize {
    match kind {
        ParamIntKind::UctTimes => {a.clamp(PARAM_UCT_TIMES_MIN, PARAM_UCT_TIMES_MAX)}
        ParamIntKind::Level => {a.clamp(PARAM_LEVEL_MIN, PARAM_LEVEL_MAX)}
    }
}
#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash)]
pub enum ParamBoolKind {
    InstanceRunning,
    AllowBackgroundThink,
    InThink,
}
fn param_bool_default(kind:ParamBoolKind) -> bool {
    match kind {
        ParamBoolKind::InstanceRunning => {false}
        ParamBoolKind::AllowBackgroundThink => {false}
        ParamBoolKind::InThink => {false}
    }
}
enum InstanceTask {
    Step(Piece, Board),
}
struct InstanceYieldFns {
    cid:ClientId,
}
impl InstanceYieldFns {
    fn new(cid:ClientId) -> Self {Self {cid}}
    fn mapchars(a:String) -> String {
        a.replace(" ", "\\s").replace("\n", "\\n")
    }
}
impl engine_ai::YieldFns for InstanceYieldFns {
    fn report(&self, text:String) {
        engine_reply(ReplyMessage::Report(self.cid, Self::mapchars(text)));
    }
    fn say(&self, text:String) {
        engine_reply(ReplyMessage::Say(self.cid, Self::mapchars(text)));
    }
    fn debug(&self, text:String) {
        engine_reply(ReplyMessage::Debug(self.cid, Self::mapchars(text)));
    }
}
struct Shared {
    running: AtomicBool,
    message_takes: Queue<TakeMessage>,
    message_replys: Queue<ReplyMessage>,
    main_handle: Mutex<Option<JoinHandle<()>>>,
    param_ints: Vec<UltraHashMap<ParamIntKind,isize>>,
    param_bools: Vec<UltraHashMap<ParamBoolKind,bool>>,
    instance_handles: IndexedList<JoinHandle<()>>,
    instance_tasks: Vec<Queue<InstanceTask>>,
    instance_yield_fns: Vec<InstanceYieldFns>,
}
impl Shared {
    fn param_int_get(&self, cid:ClientId, kind:ParamIntKind) -> isize {
        debug_assert!(cid < INSTANCE_MAX_NUMS);
        self.param_ints[cid].get(&kind).unwrap_or(param_int_default(kind))
    }
    fn param_int_set(&self, cid:ClientId, kind:ParamIntKind, to:isize) {
        debug_assert!(cid < INSTANCE_MAX_NUMS);
        let a = param_int_clamp(kind, to);
        self.param_ints[cid].insert(kind, a);
    }
    fn param_bool_get(&self, cid:ClientId, kind:ParamBoolKind) -> bool {
        debug_assert!(cid < INSTANCE_MAX_NUMS);
        self.param_bools[cid].get(&kind).unwrap_or(param_bool_default(kind))
    }
    fn param_bool_set(&self, cid:ClientId, kind:ParamBoolKind, to:bool) {
        debug_assert!(cid < INSTANCE_MAX_NUMS);
        self.param_bools[cid].insert(kind, to);
    }
    fn instance_task_push(&self, cid:ClientId, task:InstanceTask) -> bool {
        debug_assert!(cid < INSTANCE_MAX_NUMS);
        self.instance_tasks[cid].enqueue(task)
    }
    fn instance_task_poll(&self, cid:ClientId) -> Option<InstanceTask> {
        debug_assert!(cid < INSTANCE_MAX_NUMS);
        self.instance_tasks[cid].dequeue()
    }
}
static SHARED:LazyLock<Shared> = LazyLock::new(||{
    let mut param_ints = Vec::new();
    param_ints.resize_with(INSTANCE_MAX_NUMS, ||{UltraHashMap::with_capacity(PARAMS_PART_SIZE)});
    let mut param_bools = Vec::new();
    param_bools.resize_with(INSTANCE_MAX_NUMS, ||{UltraHashMap::with_capacity(PARAMS_PART_SIZE)});
    let instance_handles = IndexedList::new(INSTANCE_MAX_NUMS);
    let mut instance_tasks = Vec::with_capacity(INSTANCE_MAX_NUMS);
    instance_tasks.resize_with(INSTANCE_MAX_NUMS, ||{Queue::new(INSTANCE_TASK_CAPACITY)});
    let mut instance_yield_fns = Vec::with_capacity(INSTANCE_MAX_NUMS);
    for i in 0..INSTANCE_MAX_NUMS {
        instance_yield_fns.push(InstanceYieldFns::new(i));
    }
    Shared {
        running: AtomicBool::new(false),
        message_takes: Queue::new(MESSAGE_QUEUE_MAX_SIZE),
        message_replys: Queue::new(MESSAGE_QUEUE_MAX_SIZE),
        main_handle: Mutex::new(None),
        param_ints, param_bools,
        instance_handles, instance_tasks, instance_yield_fns,
    }
});


pub enum TakeMessage {
    SetI(ClientId, ParamIntKind,isize),
    SetB(ClientId, ParamBoolKind,bool),
    Step(ClientId, Piece, Board),
    StopThink(ClientId),
    InThink(ClientId),
}
pub enum ReplyMessage {
    Move(ClientId, Loc),
    Pass(ClientId),
    Report(ClientId, String),
    Say(ClientId, String),
    Debug(ClientId, String),
    Ready(ClientId, String),
    InThinkYes(ClientId),
    InThinkNo(ClientId),
}

fn engine_reply(msg:ReplyMessage) {
    SHARED.message_replys.enqueue(msg);
}

fn engine_take() -> Result<()> {
    if let Some(take) = SHARED.message_takes.dequeue() {
        match take {
            TakeMessage::SetI(cid, kind, a) => {
                SHARED.param_int_set(cid, kind, a);
                log::info!("[engine|instance:{}] set-i {:?} to {}", cid, kind, a);
            }
            TakeMessage::SetB(cid, kind, a) => {
                SHARED.param_bool_set(cid, kind, a);
                log::info!("[engine|instance:{}] set-b {:?} to {}", cid, kind, a);
            }
            TakeMessage::Step(cid, pcur, board) => {
                SHARED.instance_task_push(cid, InstanceTask::Step(pcur, board));
            }
            TakeMessage::InThink(cid) => {
                if SHARED.param_bool_get(cid, ParamBoolKind::InThink) {
                    engine_reply(ReplyMessage::InThinkYes(cid));
                } else {
                    engine_reply(ReplyMessage::InThinkNo(cid));
                }
            }
            TakeMessage::StopThink(cid) => {
                SHARED.param_bool_set(cid, ParamBoolKind::InThink, false);
            }
        }
    }
    Ok(())
}

fn engine_instance(cid:ClientId) {
    log::info!("[engine-instance|client:{}] start", cid);
    SHARED.param_bool_set(cid, ParamBoolKind::InstanceRunning, true);
    engine_reply(ReplyMessage::Ready(cid, format!("sslash-local-{}", VERSION)));
    let mut idle_sync = utils::FrameSync::with_ms(ENGINE_IDLE_MS);
    while SHARED.running.load(Ordering::Relaxed) && SHARED.param_bool_get(cid, ParamBoolKind::InstanceRunning) {
        if let Some(task) = SHARED.instance_task_poll(cid) {
            match task {
                InstanceTask::Step(pcur, board) => {
                    SHARED.param_bool_set(cid, ParamBoolKind::InThink, true);
                    let uct_times = SHARED.param_int_get(cid, ParamIntKind::UctTimes) as usize;
                    let level = SHARED.param_int_get(cid, ParamIntKind::Level) as usize;
                    if let Some(loc) = engine_ai::step(pcur, &board, uct_times, level, &SHARED.instance_yield_fns[cid]) {
                        engine_reply(ReplyMessage::Move(cid, loc));
                    } else {
                        engine_reply(ReplyMessage::Pass(cid));
                    }
                    SHARED.param_bool_set(cid, ParamBoolKind::InThink, false);
                }
            }
        } else {
            idle_sync.clock_now().sync();
        }
    }
    SHARED.param_bool_set(cid, ParamBoolKind::InstanceRunning, false);
    log::info!("[engine-instance|client:{}] end", cid);
}

fn engine_main() {
    fn check_alive() {
        for i in 0..INSTANCE_MAX_NUMS {
            let cid = i as ClientId;
            if SHARED.param_bool_get(cid, ParamBoolKind::InstanceRunning) {
                let slot_is_none = SHARED.instance_handles.get(i).is_none();
                if slot_is_none {
                    SHARED.param_bool_set(cid, ParamBoolKind::InstanceRunning, false);
                    log::warning!("[engine|client:{}] instance no take end signal, but has end", cid);
                }
            } else {
                let slot_is_some = SHARED.instance_handles.get(i).is_some();
                if slot_is_some {
                    free(cid);
                    log::warning!("[engine|client:{}] instance take end signal, but no end", cid);
                }
            }
        }
    }
    log::info!("[engine] start");
    SHARED.running.store(true, Ordering::Relaxed);
    let mut frame_sync = utils::FrameSync::with_ms(ENGINE_MAIN_MS);
    while SHARED.running.load(Ordering::Relaxed) {
        if let Err(e) = engine_take() {
            log::error!("[engine] error with in take message: {}", e);
            break;
        }
        check_alive();
        frame_sync.sync();
    }
    SHARED.running.store(false, Ordering::Relaxed);
    log::info!("[engine] end");
}

pub fn alloc(cid:ClientId) -> Result<()> {
    debug_assert!(cid < INSTANCE_MAX_NUMS);
    let slot = SHARED.instance_handles.get(cid);
    if slot.is_some() {
        debug_assert!(SHARED.param_bool_get(cid, ParamBoolKind::InstanceRunning));
        Err(anyhow!("instance has alloc"))
    } else {
        debug_assert!(! SHARED.param_bool_get(cid, ParamBoolKind::InstanceRunning));
        let _ = SHARED.instance_handles.set(cid, thread::spawn(move || {engine_instance(cid)}));
        log::info!("[engine|client:{}] has alloc instance", cid);
        Ok(())
    }
}
pub fn free(cid:ClientId) {
    debug_assert!(cid < INSTANCE_MAX_NUMS);
    let slot = SHARED.instance_handles.remove(cid);
    if let Some(handle) = slot {
        SHARED.param_bool_set(cid, ParamBoolKind::InstanceRunning, false);
        if let Err(_) = handle.join() {
            log::warning!("[engine|client:{}] instance thread incorrect fuck", cid)
        }
        log::info!("[engine|client:{}] has free instance", cid);
    }
}
pub fn instance_is_alive(cid:ClientId) -> bool {
    SHARED.param_bool_get(cid, ParamBoolKind::InstanceRunning)
}

pub fn _init(config:&Config) -> Result<()> {
    debug_assert!(! is_running());
    if let Err(e) = engine_ai::_init(config) {
        log::error!("[engine] ai initialize failure: {}", e);
        Err(e)
    } else {
        let mut mh = SHARED.main_handle.lock().unwrap();
        mh.replace(thread::spawn(engine_main));
        while ! is_running() {thread::sleep(Duration::from_millis(1));}
        Ok(())
    }
}
pub fn _raii() {
    let mut a = SHARED.main_handle.lock().unwrap();
    if let Some(handle) = a.take() {
        end();
        handle.join().unwrap();
    }
    engine_ai::_raii();
}

pub fn poll() -> Option<ReplyMessage> {
    SHARED.message_replys.dequeue()
}

pub fn send(to_engine:TakeMessage) -> bool {
    SHARED.message_takes.enqueue(to_engine)
}

pub fn is_running() -> bool {
    SHARED.running.load(Ordering::Relaxed)
}

pub fn end() {
    for i in 0..INSTANCE_MAX_NUMS {
        free(i as ClientId);
    }
    SHARED.running.store(false, Ordering::Relaxed);
}
